﻿using System;

namespace Sencha.Serialization.Bson
{
	public static class BsonReaderExtentions
	{
		public static BsonObjectId ReadObjectId(this IBsonReader reader, bool advance = true)
		{
			if (reader == null)
				throw new NullReferenceException();
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			var value = default(BsonObjectId);
			if (reader.Token == BsonToken.ObjectId)
				value = reader.Value.AsObjectId;
			else
				throw new Exceptions.UnexpectedBsonToken(reader, BsonToken.ObjectId);

			if (!reader.IsEndOfStream() && advance)
				reader.NextToken();

			return value;
		}
		public static BsonObjectId? ReadObjectIdOrNull(this IBsonReader reader, bool advance = true)
		{
			if (reader == null)
				throw new NullReferenceException();
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			var value = default(BsonObjectId?);
			if (reader.Token == BsonToken.Null)
			{
				value = null;
				goto next;
			}
			else if (reader.Token == BsonToken.ObjectId)
				value = reader.Value.AsObjectId;
			else
				throw new Exceptions.UnexpectedBsonToken(reader, BsonToken.ObjectId);
		next:
			if (!reader.IsEndOfStream() && advance)
				reader.NextToken();

			return value;
		}
		public static BsonTimestamp ReadTimestamp(this IBsonReader reader, bool advance = true)
		{
			if (reader == null)
				throw new NullReferenceException();
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			var value = default(BsonTimestamp);
			if (reader.Token == BsonToken.Timestamp)
				value = reader.Value.AsTimestamp;
			else
				throw new Exceptions.UnexpectedBsonToken(reader, BsonToken.Timestamp);

			if (!reader.IsEndOfStream() && advance)
				reader.NextToken();

			return value;
		}
		public static BsonTimestamp? ReadTimestampOrNull(this IBsonReader reader, bool advance = true)
		{
			if (reader == null)
				throw new NullReferenceException();
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			var value = default(BsonTimestamp?);
			if (reader.Token == BsonToken.Null)
			{
				value = null;
				goto next;
			}
			else if (reader.Token == BsonToken.Timestamp)
				value = reader.Value.AsTimestamp;
			else
				throw new Exceptions.UnexpectedBsonToken(reader, BsonToken.Timestamp);
		next:
			if (!reader.IsEndOfStream() && advance)
				reader.NextToken();

			return value;
		}
		public static BsonDateTime ReadDateTime(this IBsonReader reader, bool advance = true)
		{
			if (reader == null)
				throw new NullReferenceException();
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			var value = default(BsonDateTime);
			if (reader.Token == BsonToken.DateTime)
				value = reader.Value.AsDateTime;
			else
				throw new Exceptions.UnexpectedBsonToken(reader, BsonToken.DateTime);

			if (!reader.IsEndOfStream() && advance)
				reader.NextToken();

			return value;
		}
		public static BsonDateTime? ReadDateTimeOrNull(this IBsonReader reader, bool advance = true)
		{
			if (reader == null)
				throw new NullReferenceException();
			System.Diagnostics.Contracts.Contract.EndContractBlock();

			var value = default(BsonDateTime?);
			if (reader.Token == BsonToken.Null)
			{
				value = null;
				goto next;
			}
			else if (reader.Token == BsonToken.DateTime)
				value = reader.Value.AsDateTime;
			else
				throw new Exceptions.UnexpectedBsonToken(reader, BsonToken.DateTime);
		next:
			if (!reader.IsEndOfStream() && advance)
				reader.NextToken();

			return value;
		}
		public static BsonJavaScriptWithScope ReadJavaScriptWithScope(this IBsonReader reader)
		{
			if (reader == null)
				throw new NullReferenceException();

			if (reader.Token == BsonToken.Null)
				return null;

			if (reader.Token != BsonToken.JavaScriptWithScope && reader.Token != BsonToken.BeginDocument)
				throw new Exceptions.UnexpectedBsonToken(reader, BsonToken.JavaScriptWithScope, BsonToken.BeginDocument);

			var scopedCode = reader.Value.AsJavaScriptWithScope;
			return scopedCode;
		}
		public static BsonRegex ReadRegex(this IBsonReader reader, bool advance = true)
		{
			if (reader == null)
				throw new NullReferenceException();

			if (reader.Token == BsonToken.Null)
				return null;

			if (reader.Token != BsonToken.Regex)
				throw new Exceptions.UnexpectedBsonToken(reader, BsonToken.Regex);

			var regex = reader.Value.AsRegex;

			if (!reader.IsEndOfStream() && advance)
				reader.NextToken();

			return regex;
		}
		public static BsonBinary ReadBinary(this IBsonReader reader, bool advance = true)
		{
			if (reader == null)
				throw new NullReferenceException();

			if (reader.Token == BsonToken.Null)
				return null;

			if (reader.Token != BsonToken.Binary)
				throw new Exceptions.UnexpectedBsonToken(reader, BsonToken.Binary);

			var binary = reader.Value.AsBinary;

			if (!reader.IsEndOfStream() && advance)
				reader.NextToken();

			return binary;
		}
		public static BsonJavaScript ReadJavaScript(this IBsonReader reader, bool advance = true)
		{
			if (reader == null)
				throw new NullReferenceException();

			if (reader.Token == BsonToken.Null)
				return null;

			if (reader.Token != BsonToken.JavaScript)
				throw new Exceptions.UnexpectedBsonToken(reader, BsonToken.JavaScript);

			var value = reader.Value.AsJavaScript;

			if (!reader.IsEndOfStream() && advance)
				reader.NextToken();

			return value;
		}
		public static BsonSymbol ReadSymbol(this IBsonReader reader, bool advance = true)
		{
			if (reader == null)
				throw new NullReferenceException();

			if (reader.Token == BsonToken.Null)
				return null;

			if (reader.Token != BsonToken.Symbol)
				throw new Exceptions.UnexpectedBsonToken(reader, BsonToken.Symbol);

			var value = reader.Value.AsSymbol;

			if (!reader.IsEndOfStream() && advance)
				reader.NextToken();

			return value;
		}
	}
}
